package top.linyuxb.lycloud.gateway.starter.controller;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.linyuxb.lycloud.common.constant.LyRpcRobinType;
import top.linyuxb.lycloud.common.dto.LyRpcServerDto;
import top.linyuxb.lycloud.common.properties.LyGatewayProperties;

import java.net.HttpCookie;
import java.util.*;

/**
 * Created by Intellij IDEA.
 *
 * @Author: linyuxb
 * @Date: 2024/8/20 11:25
 * @Desc: 网关客户端控制器
 */
@Slf4j
@RestController
@RequestMapping("${ly-gateway.router.prefix}")
public class LyGatewayRouterController {
    @Resource
    private LyGatewayProperties lyRpcClientProperties;

    /**
     * rpc 服务调用
     *
     * @return
     */
    @RequestMapping(value = "/**")
    public Object gatewayAll(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        // 获取ip地址
        String ipAddress = request.getRemoteAddr();
        // 去除网关路由
        String route = requestURI.replace(lyRpcClientProperties.getRouter().getPrefix(), StringUtils.EMPTY);
        LyGatewayProperties.LyGatewayRouter thisRouter = null;
        for (LyGatewayProperties.LyGatewayRouter router : lyRpcClientProperties.getRouter().getRouters()) {
            for (LyGatewayProperties.LyGatewayPredicate predicate : router.getPredicates()) {
                String predicatePate = predicate.getPath().replace("*", ".*?");
                if (ReUtil.isMatch(predicatePate, route)) {
                    thisRouter = router;
                    break;
                }
            }
            if (Objects.nonNull(thisRouter)) {
                break;
            }
        }
        if (Objects.nonNull(thisRouter)) {
            String uri = thisRouter.getUri();
            String server = StringUtils.EMPTY;
            LyRpcRobinType robinByType = null;
            if (uri.startsWith("lb://")) {
                server = uri.replace("lb://", StringUtils.EMPTY);
                robinByType = LyRpcRobinType.getRobinByType(thisRouter.getRibbon());
            }
            if (CollectionUtils.isNotEmpty(thisRouter.getFilters())) {
                for (LyGatewayProperties.LyGatewayFilter filter : thisRouter.getFilters()) {
                    if (Objects.nonNull(filter.getStripPrefix()) && filter.getStripPrefix() > 0) {
                        String[] split = requestURI.split("/");
                        StringBuilder builder = new StringBuilder(StringUtils.EMPTY);
                        for (int i = filter.getStripPrefix() - 1; i < split.length; i++) {
                            builder.append("/").append(split[i]);
                        }
                        requestURI = builder.toString();
                    }
                    if (StringUtils.isNotBlank(filter.getPrefixPath())) {
                        requestURI = filter.getPrefixPath() + requestURI;
                    }
                }
            }
            Map<String, List<String>> thisHeaderMap = MapUtil.newHashMap();
            if (lyRpcClientProperties.getTLog().getOpen()) {
                thisHeaderMap.put(
                        lyRpcClientProperties.getTLog().getTraceId(),
                        ListUtil.toList(MDC.get(lyRpcClientProperties.getTLog().getTraceId()))
                );
            }
            Iterator<String> iterator = request.getHeaderNames().asIterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                List<String> value = ListUtil.toList(request.getHeaders(iterator.next()).asIterator());
                thisHeaderMap.put(key, value);
            }
            List<HttpCookie> cookies = Arrays.stream(request.getCookies())
                    .map(thisCookie -> new HttpCookie(thisCookie.getName(), thisCookie.getValue()))
                    .toList();
            String url = StringUtils.EMPTY;
            // 直连请求
            if (uri.startsWith("http")) {
                url = uri + requestURI;
            }
            // 注册中心请求
            if (StringUtils.isNotBlank(server)) {
                String[] split = server.split(":");
                LyRpcServerDto server1 = robinByType.getServer(split[0], split[1], ipAddress);
                if (Objects.nonNull(server1)) {
                    url = server1.getServer() + requestURI;
                }
            }
            if (StringUtils.isNotBlank(url)) {
                try (HttpResponse response = HttpRequest.of(url)
                        .method(this.getMethod(request.getMethod()))
                        .body(JSON.toJSONString(request.getParameterMap()))
                        .cookie(cookies)
                        .header(thisHeaderMap)
                        .execute()) {
                    return response.body();
                } catch (Exception e) {
                    log.error("LyGateway error", e);
                }
            }
        }
        return null;
    }

    /**
     * 获取请求类型
     *
     * @param type
     * @return
     */
    private Method getMethod(String type) {
        Method thisMethod = null;
        switch (type) {
            case "GET" -> thisMethod = Method.GET;
            case "POST" -> thisMethod = Method.POST;
            case "HEAD" -> thisMethod = Method.HEAD;
            case "OPTIONS" -> thisMethod = Method.OPTIONS;
            case "PUT" -> thisMethod = Method.PUT;
            case "DELETE" -> thisMethod = Method.DELETE;
            case "TRACE" -> thisMethod = Method.TRACE;
            case "CONNECT" -> thisMethod = Method.CONNECT;
            case "PATCH" -> thisMethod = Method.PATCH;
        }
        return thisMethod;
    }

}
